Analizando Vectores de Word2Vec

eduardofv / github

Objetivo

Word2Vec es un algoritmo de Procesamiento de Lenguaje Natural que genera representaciones de palabras en un espacio vectorial a partir de un corpus de texto. Para ello usa el “contexto” de cada palabra definido como el conjunto de n palabras alrededor de la palabra de interés. Estas representaciones se generan por medio de un modelo predictivo en el cual una red neuronal trata de predecir qué palabras aparecerán junto a una palabra determinada (modelo skipgram) o qué palabra es la aparecerá dadas otras n (modelo CBOW o Continous Bag Of World). Ver la publicación original. Se ha encontrado que estos vectores guardan ciertas relaciones semánticas y sintácticas que harían posible su uso en varias aplicaciones.

Es por esto que se vuelve necesario desarrollar una metodología para el análisis de estos vectores y que permitan crear aplicaciones de ellos. A continuación presento algunas ideas de qué se puede hacer con ellos.

El corpus y generación de embeddings.

La base para la generación de las representaciones vectoriales de palabras es un corpus de texto. Para el caso de español he utilizado el magnífico corpus recabado por Cristian Cardellino “Spanish Billion Words Corpus and Embeddings” (ver referencias) que consta de más de 1,000 millones de palabras en español. Cristian ha puesto disponible no solo el corpus ya limpio sino también los embeddings ya generados. Desafortunadamente no se encuentra el vocabulario y las frecuencias de palabras.

Para tener el vocabulario incluyendo frecuencias y un espacio de menos dimensiones corrí Word2Vec en Tensorflow en su versión optimizada con algunas modificaciones (github). Para esto se limpiaron los datos del corpus SBWE original eliminando acentos, caracteres especiales y convirtiendo a minúsculas. Se entrenó durante 15 épocas para 200 dimensiones. Se generaron embeddings para 808,854 términos con frecuencia de al menos 5 y el término UNK.

Carga de datos

#Funciones de análisis
#https://haozhu233.github.io/kableExtra/awesome_table_in_html.html#installation
library(kableExtra)
source("word2vec_analysis_functions.R")
set.seed(1234)

#Corpus 1: SBWE (español) corpus a partir de embeddings generados por w2v en TF
#  Este corpus contiene 808,854 términos en 200 dimensiones, entrenado en 15 épocas
#d <- cargar.embeddings("data/full/ds-spanish/spanish_billion_words/")
#  Usar datos previamente guardados como binarios, es mas rápido
d <- cargar.embeddings.rds("data/SBW-w2v-200d-")

#Corpus 2: SBWE (español) reducido a los primeros 100,000 elementos, para pruebas (mas rápido)
#d <- cargar.embeddings.rds("data/SBW-min-")

#Corpus 3: text8 (english) corpus a partir de embeddings generados por w2v en TF
#d <- cargar.embeddings.rds("data/text8-")

#d <- cargar.embeddings.rds("data/jobs_big1-")

emb <- d$emb
vocab <- d$vocab
rm(d)

Análisis descriptivo del espacio de componentes principales

Se efectúa un análisis de componentes principales (PCA). Esto nos permite transformar los vectores del espacio original a un espacio donde cada una de las dimensiones tiene una varianza progresivamente menor. Esto ayuda a identificar mejor dónde se pueden localizar algunos grupos de términos relacionados entre sí.

pca <- prcomp(emb)
plot(pca,type="l",col="red",main="Variance of the first principal components")

En la siguiente gráfica podemos observar la varianza acumulada por dimensión del espacio transformado. La recta azul muestra con cuántas dimensiones se alcanza el 50% de la varianza. La línea roja muestra cuánta varianza es explicada con las primeras 50 dimensiones.

pca.sum <- summary(pca)
plot(pca.sum$importance["Cumulative Proportion",],type="l",
     main="Accumulated Variance along principal components",
     xlab="Dimensions",ylab="Cumulative Proportion")
abline(v=50,col="red")
abline(h=0.5,col="blue")

Términos cercanos en el espacio original y en el espacio de componentes principales

Observamos términos cercanos para algunos casos de ejemplo. La cercanía se calcula aplicando Similitud Coseno del vector de un término con todos los demás. Observamos que hay diferencias importantes entre las similitudes encontradas en el espacio original y el espacio transformado por PCA. Si este último se normaliza no hay diferencia.

# Espacio transformado en componentes principales
pc <- as.data.frame(pca$x)
#  Espacio transformado en componentes principales y normalizado
pcn <- normalize(pc)
dslist <- list(emb,pc,pcn)
dsnames <- c("original","principal components", "PC norm" )

wlist <- list("rey","yoga","mexico")
for(w in wlist){
  i<-1
  cat(paste0("### Terminos similares a '",w,"'\n\n"))
  for(ds in dslist){
    cat(paste0("#### Espacio ",dsnames[i],"\n\n"))
    print(knitr::kable(similares(w,x=ds)[1:5,c("word","freq","cos.sim")],format = "html") 
          %>% kable_styling(bootstrap_options = "striped", full_width = F))
    i<-i+1
  }
}

Terminos similares a ‘rey’

Espacio original

word freq cos.sim
2073 principe 64544 0.7517519
24759 pretendiente 2384 0.7487800
4503 trono 26113 0.7418567
4990 reinado 22950 0.7404978
2922 emperador 44152 0.7222650

Espacio principal components

word freq cos.sim
4990 reinado 22950 0.5787233
4503 trono 26113 0.5689673
2073 principe 64544 0.5636508
24759 pretendiente 2384 0.5595046
2922 emperador 44152 0.5198701

Espacio PC norm

word freq cos.sim
4990 reinado 22950 0.5787233
4503 trono 26113 0.5689673
2073 principe 64544 0.5636508
24759 pretendiente 2384 0.5595046
2922 emperador 44152 0.5198701

Terminos similares a ‘yoga’

Espacio original

word freq cos.sim
86310 tantra 273 0.8110373
666681 viniasa 6 0.8038652
25162 zen 2327 0.7912491
678710 satyananda 6 0.7909724
670712 devanand 6 0.7890270

Espacio principal components

word freq cos.sim
86310 tantra 273 0.6154571
129021 hatha 130 0.6006844
24576 guru 2414 0.5690564
25162 zen 2327 0.5681425
666681 viniasa 6 0.5671910

Espacio PC norm

word freq cos.sim
86310 tantra 273 0.6154571
129021 hatha 130 0.6006844
24576 guru 2414 0.5690564
25162 zen 2327 0.5681425
666681 viniasa 6 0.5671910

Terminos similares a ‘mexico’

Espacio original

word freq cos.sim
1610 monterrey 84007 0.8246961
4477 veracruz 26286 0.8241511
1116 guatemala 119202 0.8214685
4086 jalisco 29463 0.8201476
2356 sinaloa 55734 0.8161345

Espacio principal components

word freq cos.sim
4477 veracruz 26286 0.6542324
1116 guatemala 119202 0.6525773
6179 chihuahua 17620 0.6495564
4086 jalisco 29463 0.6487193
1610 monterrey 84007 0.6403927

Espacio PC norm

word freq cos.sim
4477 veracruz 26286 0.6542324
1116 guatemala 119202 0.6525773
6179 chihuahua 17620 0.6495564
4086 jalisco 29463 0.6487193
1610 monterrey 84007 0.6403927

Analogías en el espacio original y en el de componentes principales

Observamos que se comportan las analogías en cada uno de los espacios. En este casi sí hay diferencias en los resultados. En general he observado mejores resultados en el espacio de componentes principales normalizado. Para términos comunes tambien se observa que hay mejores resultados si además se eliminan términos menos frecuentes del dataset.

anlist <- list(c("francia","paris","espana"), 
               c("rey","reina","hombre"), 
               c("apple","ios","microsoft"))
for(a in anlist){
  cat(paste0("### Analogias para ",paste(a,collapse = "/"),"\n\n"))
  i<-1
  for(d in dslist){
    cat(paste0("####  Espacio ",dsnames[i],"\n\n"))
    print(knitr::kable(analogia(a[1],a[2],a[3],x=d)[1:3,c("word","freq","cos.sim")],
                       format = "html") %>% 
            kable_styling(bootstrap_options = "striped", full_width = F))
    i<-i+1
  }
}

Analogias para francia/paris/espana

Espacio original

word freq cos.sim
345 madrid 330703 0.6552825
9779 gijon 9563 0.6524968
8741 vigo 11159 0.6480950

Espacio principal components

word freq cos.sim
345 madrid 330703 0.4707165
9779 gijon 9563 0.4652975
8741 vigo 11159 0.4543748

Espacio PC norm

word freq cos.sim
345 madrid 330703 0.4710429
9779 gijon 9563 0.4549730
515 barcelona 234558 0.4412346

Analogias para rey/reina/hombre

Espacio original

word freq cos.sim
32037 vendedora 1564 0.6300830
19304 betty 3551 0.6253540
16111 maravilla 4671 0.6243655

Espacio principal components

word freq cos.sim
16606 anciana 4463 0.4212914
15359 senorita 5000 0.4164873
235 mujer 426507 0.3964051

Espacio PC norm

word freq cos.sim
32037 vendedora 1564 0.4445025
16606 anciana 4463 0.4282076
15359 senorita 5000 0.4267983

Analogias para apple/ios/microsoft

Espacio original

word freq cos.sim
4572 windows 25565 0.8004357
11924 android 7292 0.7930679
14971 xbox 5214 0.7612021

Espacio principal components

word freq cos.sim
4572 windows 25565 0.7661221
11924 android 7292 0.7598521
14971 xbox 5214 0.7170435

Espacio PC norm

word freq cos.sim
4572 windows 25565 0.7303656
11924 android 7292 0.7254232
14971 xbox 5214 0.6899744

Valores extremos en algunas dimensiones

Se observan grupos identificables en los valores extremos de cada una de las dimensiones. Especialmente en dimensiones con menor varianza, los extremos son grupos aparentemente mas definidos. En otras palabras, parece que en las dimensiones que explican menos varianza los conjuntos de términos en los extremos son mas definidos.

word.plot.dimension(pcn,target.dim = "PC1",sec.dim = "PC2",max.plot = 20)

word.plot.dimension(pcn,target.dim = "PC200",sec.dim = "PC1",max.plot = 20)

word.plot.dimension(pcn,target.dim = "PC199",sec.dim = "PC1",max.plot = 20)

Análisis de grupos a partir de un término

Encontramos una metodología simple para el análisis de los términos relacionados con un término base:

  1. Se obtienen un número determinado de términos mas cercanos (similares) a partir del espacio de componentes principales normalizado.
  2. Se hace un K-Means clustering de los términos. Opcionalmente se puede realizar previamente un análsis para determinar el número de clusters que se deben generar.
  3. Se grafican en las dos primeras dimensiones utilizadas.
  4. Se genera un wordcloud por cada cluster de palabras generado.

Podemos observar que en general los grupos identifican distintos grupos de palabras relacionados entre sí de alguna forma. Hemos visto grupos de adjetivos, razas de perros, características semánticas, etc. razonablemente bien agrupadas mediante este método.

termlist <- list("perro","web","algoritmo")
termclist <- c(8,7,8)

for(w in termlist){
  cat(paste0("### Analisis para el termino: ",w,"\n\n"))
  dclust<-pca.cluster.similar(w,x = pcn,v = vocab,n=300,show.center.analysis = T)
  dclust<-pca.cluster.similar(w,x = pcn,v = vocab,n=300,centers=termclist[w==termlist])
  cluster.wordcloud(dclust,scale=c(2.75,0.5))
  cat("\n\n")
}

Analisis para el termino: perro

Analisis para el termino: web

Analisis para el termino: algoritmo